
package combat.dudes.server.classes;

import java.util.HashMap;
import java.util.Map;

import combat.dudes.DudesConfig;
import combat.dudes.protocol.data.DoublePoint;
import combat.dudes.protocol.data.PhysicalObject;
import combat.dudes.protocol.types.ProjectileType;
import combat.dudes.protocol.types.VehicleType;
import combat.dudes.server.protocol.implementation.Simulation;


/**
 * 
 * @author Gerard Krombeen, Jesper Keijzer
 * @version 1.0
 */

public final class Calculation {

	public static class MainCalc {

		/**
		 * Converts a speed from km/h to coordinates/serverLoop
		 * @author Gerard Krombeen
		 * @author Jesper Keijzer
		 * @param speed (in km/h)
		 * @return double
		 */
		public static double kmhToCpsl(double speed) {
			return ((speed / 1.852) / (3600 * (1000 / DudesConfig.SharedVars.getRefreshTime())));
		}

		/**
		 * Returns the direction (in degrees, in a range from -180 to 180) between two DoublePoints
		 * @author Gerard Krombeen
		 * @author Jesper Keijzer
		 * @author Laurens Andriessen
		 * @return double
		 */
		public static double direction(DoublePoint start, Object end) {

			DoublePoint endLoc = new DoublePoint();
			if(end.getClass().getSuperclass().equals(PhysicalObject.class)) {
				PhysicalObject physobj = (PhysicalObject) end;
				endLoc = physobj.getPosition();
			} else endLoc = (DoublePoint) end;

			double direction = (90 - (Math.toDegrees(Math.atan2(endLoc.getY() - start.getY(), endLoc.getX() - start.getX()))));
			if(direction > 180)
				direction -= 360;
			return direction;
		}

		/**
		 * Returns the max nextDirection (in degrees, in a range from -180 to 180) between two DoublePoints depends on a max turnangle of a vehicle type and it's current direction
		 * @author Jesper Keijzer
		 * @return double
		 */
		public static double nextDirection(double currentDirection, DoublePoint start, Object end, VehicleType type, int health) {

			DoublePoint endLoc = new DoublePoint();
			if(end.getClass().getSuperclass().equals(PhysicalObject.class)) {
				PhysicalObject physobj = (PhysicalObject) end;
				endLoc = physobj.getPosition();
			} else endLoc = (DoublePoint) end;

			double targetDirection = direction(start, endLoc);

			if(currentDirection < 0.0)
				currentDirection += 360.0;

			if(targetDirection < 0.0)
				targetDirection += 360.0;

			double difference = targetDirection - currentDirection;
			double max = (DudesConfig.SharedVars.getMaxTurnAngle(type) / (1000 / DudesConfig.SharedVars.getRefreshTime())) * Simulation.instance().getSimSpeed();
			if(health < 60)
				max /= 2;
			double newDirection;

			// take the short side
			if(difference > 180.0)
				difference -= 360;

			if(difference > max)
				newDirection = currentDirection + max;
			else if(difference < (max * -1.0))
				newDirection = currentDirection - max;
			else newDirection = currentDirection + difference;
			if(newDirection < 0.0)
				newDirection += 360.0;
			if(newDirection > 180.0)
				newDirection -= 360.0;
			return newDirection;
		}

		/**
		 * Returns the distance between two DoublePoints
		 * @author Gerard Krombeen
		 * @author Laurens Andriessen
		 * @return double
		 */
		public static double distance(DoublePoint start, Object end) {
			DoublePoint endLoc = new DoublePoint();
			if(end.getClass().getSuperclass().equals(PhysicalObject.class)) {
				PhysicalObject physobj = (PhysicalObject) end;
				endLoc = physobj.getPosition();
			} else endLoc = (DoublePoint) end;

			double differenceX = endLoc.getX() - start.getX();
			double differenceY = endLoc.getY() - start.getY();
			if(differenceX < 0)
				differenceX = (differenceX - (2 * differenceX));
			if(differenceY < 0)
				differenceY = (differenceY - (2 * differenceY));
			double totalsquare = Math.pow(differenceX, 2) + Math.pow(differenceY, 2);
			double distance = Math.sqrt(totalsquare);
			return distance;
		}

		/**
		 * Returns the the next position using a startPosition, speed (km/h), the simulationSpeed and an angle
		 * @author Jesper Keijzer
		 * @return double
		 */
		public static DoublePoint nextPosition(DoublePoint start, double speed, double direction) {
			double distance = kmhToCpsl(speed) * Simulation.instance().getSimSpeed();
			direction = 90 - direction;
			if(direction < 0)
				direction += 360;
			DoublePoint nextPosition = new DoublePoint((start.getX() + (distance * (Math.cos(Math.toRadians(direction))))), (start.getY() + (distance * (Math.sin(Math.toRadians(direction))))));
			if((nextPosition.getX() >= DudesConfig.SharedVars.mapSize.getX()) || (nextPosition.getY() >= DudesConfig.SharedVars.mapSize.getY()) || (nextPosition.getX() <= 0)
					|| (nextPosition.getY() <= 0))
				nextPosition = start;
			return nextPosition;
		}

		/**
		 * Returns max speed depending on damage
		 * @author Jesper Keijzer
		 * @return double
		 */
		public static double getMaxSpeed(VehicleType vehicleType, int health) {
			double max = DudesConfig.SharedVars.getMaxSpeed(vehicleType);
			if(health < 80) {
				if(health < 30)
					max = max / 4;
				else max = max / 2;
			}
			return max;
		}

		/**
		 * Returns adjust speed when its need it .
		 * @author Gerard Krombeen
		 * @return double
		 */
		public static double turningSpeed(double turningSpeed, DoublePoint startLoc, Object end, VehicleType vehicleType, double currentDirection) {

			DoublePoint endLoc = new DoublePoint();
			if(end.getClass().getSuperclass().equals(PhysicalObject.class)) {
				PhysicalObject physobj = (PhysicalObject) end;
				endLoc = physobj.getPosition();
			} else endLoc = (DoublePoint) end;

			DoublePoint centreCircle = new DoublePoint();
			boolean rightspeed = false;

			while(!rightspeed) {

				double radius = (360.0 / DudesConfig.SharedVars.getMaxTurnAngle(vehicleType)) * (turningSpeed / (2 * Math.PI));

				// calculate the middle of the 2 turning circles
				// right circle
				if(direction(startLoc, endLoc) - currentDirection < 0) {

					centreCircle.setX(startLoc.getX() + (Math.cos(currentDirection - 90.0) * radius));
					centreCircle.setY(startLoc.getY() + (Math.sin(currentDirection - 90.0) * radius));
				}

				// left circle
				if(direction(startLoc, endLoc) - currentDirection > 0) {

					centreCircle.setX(startLoc.getX() + (Math.cos(currentDirection + 90.0) * radius));
					centreCircle.setY(startLoc.getY() + (Math.sin(currentDirection + 90.0) * radius));
				}

				if(distance(centreCircle, endLoc) > radius) {
					rightspeed = true;
				}
				if(distance(centreCircle, endLoc) < radius) {
					turningSpeed -= 1.0;
				}

			}
			return turningSpeed;

		}
	}

	/**
	 * Calculation of damage consequences
	 * @author Jesper Keijzer
	 */
	public static class DamageCalc {

		public static class hitTable {
			// new DamageMaps
			private static final Map<ProjectileType, Map<ProjectileType, DamageTask>> damageProjOnProj = new HashMap<ProjectileType, Map<ProjectileType, DamageTask>>();
			private static final Map<ProjectileType, Map<VehicleType, DamageTask>> damageProjOnVehic = new HashMap<ProjectileType, Map<VehicleType, DamageTask>>();
			private static final Map<VehicleType, Map<VehicleType, DamageTask>> damageVehicOnVehic = new HashMap<VehicleType, Map<VehicleType, DamageTask>>();

			static {
				Map<ProjectileType, DamageTask> newProjMap;
				Map<VehicleType, DamageTask> newVehicMap;

				// Add mapTypes
				// damageProjOnProj
				for(ProjectileType projectileType : ProjectileType.values()) {
					newProjMap = new HashMap<ProjectileType, DamageTask>();
					for(ProjectileType selectedProjectileType : ProjectileType.values())
						newProjMap.put(selectedProjectileType, new DamageTask());
					damageProjOnProj.put(projectileType, newProjMap);

					// damageProjOnVehic
					newVehicMap = new HashMap<VehicleType, DamageTask>();
					for(VehicleType slectedvehicleType : VehicleType.values())
						newVehicMap.put(slectedvehicleType, new DamageTask());
					damageProjOnVehic.put(projectileType, newVehicMap);
				}

				// damageVehicOnVehic
				for(VehicleType vehicleType : VehicleType.values()) {
					newVehicMap = new HashMap<VehicleType, DamageTask>();
					for(VehicleType slectedvehicleType : VehicleType.values())
						newVehicMap.put(slectedvehicleType, new DamageTask());
					damageVehicOnVehic.put(vehicleType, newVehicMap);
				}

				// Fill Map values
				// damageProjOnProj
				damageProjOnProj.get(ProjectileType.MISSILE_GUIDED).get(ProjectileType.MISSILE_GUIDED).setTaskObject1(100);
				damageProjOnProj.get(ProjectileType.MISSILE_GUIDED).get(ProjectileType.MISSILE_GUIDED).setTaskObject2(100);
				damageProjOnProj.get(ProjectileType.MISSILE_GUIDED).get(ProjectileType.MISSILE_UNGUIDED).setTaskObject1(100);
				damageProjOnProj.get(ProjectileType.MISSILE_GUIDED).get(ProjectileType.MISSILE_UNGUIDED).setTaskObject2(100);
				damageProjOnProj.get(ProjectileType.MISSILE_GUIDED).get(ProjectileType.TORPEDO).setTaskObject1(0);
				damageProjOnProj.get(ProjectileType.MISSILE_GUIDED).get(ProjectileType.TORPEDO).setTaskObject2(0);
				damageProjOnProj.get(ProjectileType.MISSILE_GUIDED).get(ProjectileType.UNKNOWN).setTaskObject1(100);
				damageProjOnProj.get(ProjectileType.MISSILE_GUIDED).get(ProjectileType.UNKNOWN).setTaskObject2(100);
				damageProjOnProj.get(ProjectileType.MISSILE_UNGUIDED).get(ProjectileType.MISSILE_GUIDED).setTaskObject1(100);
				damageProjOnProj.get(ProjectileType.MISSILE_UNGUIDED).get(ProjectileType.MISSILE_GUIDED).setTaskObject2(100);
				damageProjOnProj.get(ProjectileType.MISSILE_UNGUIDED).get(ProjectileType.MISSILE_UNGUIDED).setTaskObject1(100);
				damageProjOnProj.get(ProjectileType.MISSILE_UNGUIDED).get(ProjectileType.MISSILE_UNGUIDED).setTaskObject2(100);
				damageProjOnProj.get(ProjectileType.MISSILE_UNGUIDED).get(ProjectileType.TORPEDO).setTaskObject1(0);
				damageProjOnProj.get(ProjectileType.MISSILE_UNGUIDED).get(ProjectileType.TORPEDO).setTaskObject2(0);
				damageProjOnProj.get(ProjectileType.MISSILE_UNGUIDED).get(ProjectileType.UNKNOWN).setTaskObject1(100);
				damageProjOnProj.get(ProjectileType.MISSILE_UNGUIDED).get(ProjectileType.UNKNOWN).setTaskObject2(100);
				damageProjOnProj.get(ProjectileType.TORPEDO).get(ProjectileType.MISSILE_GUIDED).setTaskObject1(0);
				damageProjOnProj.get(ProjectileType.TORPEDO).get(ProjectileType.MISSILE_GUIDED).setTaskObject2(0);
				damageProjOnProj.get(ProjectileType.TORPEDO).get(ProjectileType.MISSILE_UNGUIDED).setTaskObject1(0);
				damageProjOnProj.get(ProjectileType.TORPEDO).get(ProjectileType.MISSILE_UNGUIDED).setTaskObject2(0);
				damageProjOnProj.get(ProjectileType.TORPEDO).get(ProjectileType.TORPEDO).setTaskObject1(1);
				damageProjOnProj.get(ProjectileType.TORPEDO).get(ProjectileType.TORPEDO).setTaskObject2(1);
				damageProjOnProj.get(ProjectileType.TORPEDO).get(ProjectileType.UNKNOWN).setTaskObject1(100);
				damageProjOnProj.get(ProjectileType.TORPEDO).get(ProjectileType.UNKNOWN).setTaskObject2(100);
				damageProjOnProj.get(ProjectileType.UNKNOWN).get(ProjectileType.MISSILE_GUIDED).setTaskObject1(100);
				damageProjOnProj.get(ProjectileType.UNKNOWN).get(ProjectileType.MISSILE_GUIDED).setTaskObject2(100);
				damageProjOnProj.get(ProjectileType.UNKNOWN).get(ProjectileType.MISSILE_UNGUIDED).setTaskObject1(100);
				damageProjOnProj.get(ProjectileType.UNKNOWN).get(ProjectileType.MISSILE_UNGUIDED).setTaskObject2(100);
				damageProjOnProj.get(ProjectileType.UNKNOWN).get(ProjectileType.TORPEDO).setTaskObject1(100);
				damageProjOnProj.get(ProjectileType.UNKNOWN).get(ProjectileType.TORPEDO).setTaskObject2(100);
				damageProjOnProj.get(ProjectileType.UNKNOWN).get(ProjectileType.UNKNOWN).setTaskObject1(100);
				damageProjOnProj.get(ProjectileType.UNKNOWN).get(ProjectileType.UNKNOWN).setTaskObject2(100);

				// damageProjOnVehic
				damageProjOnVehic.get(ProjectileType.MISSILE_GUIDED).get(VehicleType.CONVOY).setTaskObject1(100);
				damageProjOnVehic.get(ProjectileType.MISSILE_GUIDED).get(VehicleType.CONVOY).setTaskObject2(20);
				damageProjOnVehic.get(ProjectileType.MISSILE_GUIDED).get(VehicleType.FRIGATE).setTaskObject1(100);
				damageProjOnVehic.get(ProjectileType.MISSILE_GUIDED).get(VehicleType.FRIGATE).setTaskObject2(20);
				damageProjOnVehic.get(ProjectileType.MISSILE_GUIDED).get(VehicleType.SUBMARINE).setTaskObject1(0);
				damageProjOnVehic.get(ProjectileType.MISSILE_GUIDED).get(VehicleType.SUBMARINE).setTaskObject2(100);
				damageProjOnVehic.get(ProjectileType.MISSILE_GUIDED).get(VehicleType.UNKNOWN).setTaskObject1(0);
				damageProjOnVehic.get(ProjectileType.MISSILE_GUIDED).get(VehicleType.UNKNOWN).setTaskObject2(100);
				damageProjOnVehic.get(ProjectileType.MISSILE_UNGUIDED).get(VehicleType.CONVOY).setTaskObject1(100);
				damageProjOnVehic.get(ProjectileType.MISSILE_UNGUIDED).get(VehicleType.CONVOY).setTaskObject2(20);
				damageProjOnVehic.get(ProjectileType.MISSILE_UNGUIDED).get(VehicleType.FRIGATE).setTaskObject1(100);
				damageProjOnVehic.get(ProjectileType.MISSILE_UNGUIDED).get(VehicleType.FRIGATE).setTaskObject2(20);
				damageProjOnVehic.get(ProjectileType.MISSILE_UNGUIDED).get(VehicleType.SUBMARINE).setTaskObject1(100);
				damageProjOnVehic.get(ProjectileType.MISSILE_UNGUIDED).get(VehicleType.SUBMARINE).setTaskObject2(0);
				damageProjOnVehic.get(ProjectileType.MISSILE_UNGUIDED).get(VehicleType.UNKNOWN).setTaskObject1(100);
				damageProjOnVehic.get(ProjectileType.MISSILE_UNGUIDED).get(VehicleType.UNKNOWN).setTaskObject2(0);
				damageProjOnVehic.get(ProjectileType.TORPEDO).get(VehicleType.CONVOY).setTaskObject1(100);
				damageProjOnVehic.get(ProjectileType.TORPEDO).get(VehicleType.CONVOY).setTaskObject2(20);
				damageProjOnVehic.get(ProjectileType.TORPEDO).get(VehicleType.FRIGATE).setTaskObject1(100);
				damageProjOnVehic.get(ProjectileType.TORPEDO).get(VehicleType.FRIGATE).setTaskObject2(20);
				damageProjOnVehic.get(ProjectileType.TORPEDO).get(VehicleType.SUBMARINE).setTaskObject1(100);
				damageProjOnVehic.get(ProjectileType.TORPEDO).get(VehicleType.SUBMARINE).setTaskObject2(100);
				damageProjOnVehic.get(ProjectileType.TORPEDO).get(VehicleType.UNKNOWN).setTaskObject1(100);
				damageProjOnVehic.get(ProjectileType.TORPEDO).get(VehicleType.UNKNOWN).setTaskObject2(0);
				damageProjOnVehic.get(ProjectileType.UNKNOWN).get(VehicleType.CONVOY).setTaskObject1(100);
				damageProjOnVehic.get(ProjectileType.UNKNOWN).get(VehicleType.CONVOY).setTaskObject2(20);
				damageProjOnVehic.get(ProjectileType.UNKNOWN).get(VehicleType.FRIGATE).setTaskObject1(100);
				damageProjOnVehic.get(ProjectileType.UNKNOWN).get(VehicleType.FRIGATE).setTaskObject2(20);
				damageProjOnVehic.get(ProjectileType.UNKNOWN).get(VehicleType.SUBMARINE).setTaskObject1(100);
				damageProjOnVehic.get(ProjectileType.UNKNOWN).get(VehicleType.SUBMARINE).setTaskObject2(100);
				damageProjOnVehic.get(ProjectileType.UNKNOWN).get(VehicleType.UNKNOWN).setTaskObject1(0);
				damageProjOnVehic.get(ProjectileType.UNKNOWN).get(VehicleType.UNKNOWN).setTaskObject2(100);

				// damageVehicOnVehic
				damageVehicOnVehic.get(VehicleType.CONVOY).get(VehicleType.CONVOY).setTaskObject1(100);
				damageVehicOnVehic.get(VehicleType.CONVOY).get(VehicleType.CONVOY).setTaskObject2(100);
				damageVehicOnVehic.get(VehicleType.CONVOY).get(VehicleType.FRIGATE).setTaskObject1(100);
				damageVehicOnVehic.get(VehicleType.CONVOY).get(VehicleType.FRIGATE).setTaskObject2(100);
				damageVehicOnVehic.get(VehicleType.CONVOY).get(VehicleType.SUBMARINE).setTaskObject1(0);
				damageVehicOnVehic.get(VehicleType.CONVOY).get(VehicleType.SUBMARINE).setTaskObject2(0);
				damageVehicOnVehic.get(VehicleType.CONVOY).get(VehicleType.UNKNOWN).setTaskObject1(100);
				damageVehicOnVehic.get(VehicleType.CONVOY).get(VehicleType.UNKNOWN).setTaskObject2(0);
				damageVehicOnVehic.get(VehicleType.FRIGATE).get(VehicleType.CONVOY).setTaskObject1(100);
				damageVehicOnVehic.get(VehicleType.FRIGATE).get(VehicleType.CONVOY).setTaskObject2(100);
				damageVehicOnVehic.get(VehicleType.FRIGATE).get(VehicleType.FRIGATE).setTaskObject1(100);
				damageVehicOnVehic.get(VehicleType.FRIGATE).get(VehicleType.FRIGATE).setTaskObject2(100);
				damageVehicOnVehic.get(VehicleType.FRIGATE).get(VehicleType.SUBMARINE).setTaskObject1(0);
				damageVehicOnVehic.get(VehicleType.FRIGATE).get(VehicleType.SUBMARINE).setTaskObject2(0);
				damageVehicOnVehic.get(VehicleType.FRIGATE).get(VehicleType.UNKNOWN).setTaskObject1(100);
				damageVehicOnVehic.get(VehicleType.FRIGATE).get(VehicleType.UNKNOWN).setTaskObject2(0);
				damageVehicOnVehic.get(VehicleType.SUBMARINE).get(VehicleType.CONVOY).setTaskObject1(0);
				damageVehicOnVehic.get(VehicleType.SUBMARINE).get(VehicleType.CONVOY).setTaskObject2(0);
				damageVehicOnVehic.get(VehicleType.SUBMARINE).get(VehicleType.FRIGATE).setTaskObject1(0);
				damageVehicOnVehic.get(VehicleType.SUBMARINE).get(VehicleType.FRIGATE).setTaskObject2(0);
				damageVehicOnVehic.get(VehicleType.SUBMARINE).get(VehicleType.SUBMARINE).setTaskObject1(100);
				damageVehicOnVehic.get(VehicleType.SUBMARINE).get(VehicleType.SUBMARINE).setTaskObject2(100);
				damageVehicOnVehic.get(VehicleType.SUBMARINE).get(VehicleType.UNKNOWN).setTaskObject1(100);
				damageVehicOnVehic.get(VehicleType.SUBMARINE).get(VehicleType.UNKNOWN).setTaskObject2(0);
				damageVehicOnVehic.get(VehicleType.UNKNOWN).get(VehicleType.CONVOY).setTaskObject1(0);
				damageVehicOnVehic.get(VehicleType.UNKNOWN).get(VehicleType.CONVOY).setTaskObject2(100);
				damageVehicOnVehic.get(VehicleType.UNKNOWN).get(VehicleType.FRIGATE).setTaskObject1(0);
				damageVehicOnVehic.get(VehicleType.UNKNOWN).get(VehicleType.FRIGATE).setTaskObject2(100);
				damageVehicOnVehic.get(VehicleType.UNKNOWN).get(VehicleType.SUBMARINE).setTaskObject1(0);
				damageVehicOnVehic.get(VehicleType.UNKNOWN).get(VehicleType.SUBMARINE).setTaskObject2(100);
				damageVehicOnVehic.get(VehicleType.UNKNOWN).get(VehicleType.UNKNOWN).setTaskObject1(0);
				damageVehicOnVehic.get(VehicleType.UNKNOWN).get(VehicleType.UNKNOWN).setTaskObject2(0);
			}

			// Projectile vs. projectile
			public static DamageTask getDamage(ProjectileType projectileA, ProjectileType projectileB) {
				return damageProjOnProj.get(projectileA).get(projectileB);
			}

			// Projectile vs. vehicle
			public static DamageTask getDamage(ProjectileType projectileA, VehicleType vehicleB) {
				return damageProjOnVehic.get(projectileA).get(vehicleB);
			}

			// Vehicle vs. vehicle
			public static DamageTask getDamage(VehicleType vehicleA, VehicleType vehicleB) {
				return damageVehicOnVehic.get(vehicleA).get(vehicleB);
			}
		}

		// Check if the object is a vehicle or projectile and nothing else
		public static boolean isVehicleOrProjectile(PhysicalObject physicalObject) {
			switch(physicalObject.getType()) {
				case PROJECTILE:
					return true;
				case VEHICLE:
					return true;
				default:
					return false;
			}
		}
	}

}
